/*
 * 基于线程池的任务处理->
 * 1. 一个线程用于生产任务，存放在任务队列中
 * 2. 线程池中创建多个消费线程，用于拿取任务队列中的任务
 */
#pragma once
#include <iostream>
#include <pthread.h>
#include <queue>
#include <unistd.h>

namespace MyThreadPool
{
    const int g_num_defult = 5;

    template <class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue;
        // 加上一把互斥锁，防止线程在任务队列中 放/拿 任务时，防止多线程同时访问队列
        pthread_mutex_t _mtx;
        // 引入条件变量，防止任务队列中没有任务时，线程还在访问队列
        pthread_cond_t _cond;

        // 单例模式定义一个类类型的static成员指针
        static ThreadPool<T> *_inis;
        // 同时单例模式的构造函数需要私有，拷贝构造和赋值都不能存在
    private:
        ThreadPool(int num = g_num_defult)
            : _num(num)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_cond, nullptr);
        };

        ThreadPool(const ThreadPool<T> &tp) = delete;
        ThreadPool<T> &operator=(ThreadPool<T> &tp) = delete;

    public:
        // 定义一个static函数(必须是static函数，因为是成员函数，非静态成员函数调用需要类对象)，用于定义一个类对象
        static ThreadPool<T> *GetInstance()
        {
            // 加锁保证多线程只调用一次new，申请一个对象
            // 双重判断防止重复申请锁和解锁的过程
            pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
            if (_inis == nullptr)
            {
                pthread_mutex_lock(&mtx);
                if (_inis == nullptr)
                {
                    _inis = new ThreadPool<T>();
                    _inis->InitThreadPool();
                }
                pthread_mutex_unlock(&mtx);
            }
            return _inis;
        }

    public:
        void Lock()
        {
            pthread_mutex_lock(&_mtx);
        }

        void UnLock()
        {
            pthread_mutex_unlock(&_mtx);
        }

        bool IsEmpty()
        {
            return _task_queue.empty();
        }

        void Wait()
        {
            pthread_cond_wait(&_cond, &_mtx);
        }

        void Wakeup()
        {
            pthread_cond_signal(&_cond);
        }

    public:
        static void *RunThread(void *args)
        {
            pthread_detach(pthread_self());

            MyThreadPool::ThreadPool<T> *tp = (MyThreadPool::ThreadPool<T> *)args;
            while (true)
            {
                T t;
                tp->Lock();
                while (tp->IsEmpty())
                {
                    // 线程池如果检测到任务队列中没有任务，就不应该继续访问任务队列
                    // 因为一直获取锁，检测，释放锁会消耗大量时间，做无用功，因此需要将线程挂起————引入条件变量
                    tp->Wait();
                }
                tp->PopTask(&t);
                tp->UnLock();
                // 线程拿到任务之后，归还锁的好处：当前进程在执行任务的同时，其余线程可以拿到锁
                t();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for (int i = 0; i < _num; ++i)
            {
                pthread_create(&tid, nullptr, RunThread, (void *)this);
            }
        }

        void PushTask(const T &in)
        {
            Lock();
            _task_queue.push(in);
            UnLock();
            // 往任务队列中放任务后，需要唤醒线程池的线程
            Wakeup();
        }

        void PopTask(T *out)
        {
            *out = _task_queue.front();
            _task_queue.pop();
        }

        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_cond);
        };
    };

    template <class T>
    ThreadPool<T> *ThreadPool<T>::_inis = nullptr;
}